package com.indigon.parkStory.model.objects
{
	import com.indigon.parkStory.data.objects.LillyData;
	import com.indigon.parkStory.model.GameEvent;
	import com.indigon.parkStory.model.ModelMain;
	import com.indigon.utils.PointUtil;
	
	import flash.geom.Point;

	public class LillyModel extends ObjectModel
	{
		private static const INIT_MAX_LIN_VEL : Number = 0.01;
		private static const INIT_MAX_ANG_VEL : Number = 0.1;
		private static const ELASTIC_VALUE    : Number = 0.95;
		private static const MAX_LIN_VEL      : Number = 2.0;
		private static const MAX_ANG_VEL      : Number = 15.0;
		private static const LINEAR_DAMPING   : Number = 0.01;
		private static const ANGULAR_DAMPING  : Number = 0.015;
		private static const MIN_ANG_VEL	  : Number = 0.1;
		private static const COLLISION_THRESH : Number = 500;
		private static const TURTLE_TIME	  : Number = 1.0;
		private static const TURTLE_FORCE	  : Number = 0.75;
		public  static const FADE_TIME		  : Number = 0.5;
		
		public var data : LillyData;
		
		public var hasStar : Boolean;
		public var hasGoal : Boolean;
		public var hasFrog : Boolean;
		
		private var _turtleEnabled  : Boolean;
		private var _turtleTimer    : uint;
		private var _maxTurtleTimer : uint;
		private var _maxFadingTime  : uint;
		private var _fadingTimer    : uint;
		private var _linVel         : Point;
		private var _forces         : Vector.<Point>;
		
		public var boundVector : Point;
		
		public function LillyModel(data:LillyData, model:ModelMain)
		{
			super(model);
			
			this._model = model;
			this.data = data;
			
			this.hasFrog = data.hasFrog;
			this.hasStar = data.hasStar;
			this.hasGoal = data.hasGoal;
			
			this._linVel = new Point(-INIT_MAX_LIN_VEL + (Math.random() * INIT_MAX_LIN_VEL * 2), -INIT_MAX_LIN_VEL + (Math.random() * INIT_MAX_LIN_VEL * 2));
			this._forces = new Vector.<Point>();
			this.angVel = (!isForceable) ? 0 : (-INIT_MAX_ANG_VEL + (2 *Math.random() * INIT_MAX_ANG_VEL));
			
			this._maxFadingTime  = FADE_TIME * model.framerate;
			this._maxTurtleTimer = TURTLE_TIME * model.framerate;
		}
		
		public override function uninit():void
		{
			_linVel = null;
			data   = null;
			_forces = null;
			super.uninit();
		}
		
		public function checkCollision(lilly:LillyModel):void
		{
			var d 		 : Point  = lilly.pos.subtract(this.pos);
			var dist_sqr : Number = (d.x * d.x) + (d.y * d.y);
			var rad      : Number = this.data.radius + lilly.data.radius;
			var rad_sqr  : Number = rad * rad;
			
			// if there's a collision.
			if (dist_sqr <= rad_sqr)
			{
				var dist  : Number = Math.sqrt(dist_sqr);
				var nColl : Point  = PointUtil.multiple(d, 1 / dist);
				
				var dColl   : Number = rad - dist;
				var imThis  : Number = (this.data.mass == 0.0)  ? 0 : 1 / this.data.mass;
				var imOther : Number = (lilly.data.mass == 0.0) ? 0 : 1 / lilly.data.mass;
				var sepVec  : Point = PointUtil.multiple(nColl, dColl / (imThis + imOther));
				
				this.pos  = this.pos.subtract(PointUtil.multiple(sepVec, imThis));
				lilly.pos = lilly.pos.add(PointUtil.multiple(sepVec, imOther));
				
				var vColl : Point  = lilly._linVel.subtract(this._linVel);
				var vn    : Number = (vColl.x * nColl.x) + (vColl.y * nColl.y); 
				
				if (vn <= 0.0)
				{
					var j       : Number = -(1.0 + ELASTIC_VALUE) * vn / (imThis + imOther);
					var impulse : Point  = PointUtil.multiple(nColl, j);
					this._linVel  = this._linVel.subtract(PointUtil.multiple(impulse, imThis));
					lilly._linVel = lilly._linVel.add(PointUtil.multiple(impulse, imOther));
				}
				
				this.angVel  *= 0.5;
				lilly.angVel *= 0.5;
				if (j >= COLLISION_THRESH)
				{
					_model.addEvent(GameEvent.LILLY_COLLISION);
				}
			}
		}
		
		public function starCollected():void
		{
			hasStar = false;
			_model.score.collectStar();
			_model.addEvent(GameEvent.STAR_COLLECTED, this);
		}
		
		public function goalCollected():void
		{
			hasGoal = false;
			_model.score.collectGoal();
			_model.addEvent(GameEvent.GOAL_COLLECTED, this);
		}
		
		public function jumpOff(pushForce:Point):void
		{
			if (isForceable)
			{
				_forces.push(pushForce);
			}
			
			hasFrog = false;
			
			if (data.isSingle)
			{
				isDead = true;
				_model.addEvent(GameEvent.LILLY_SINGLE, this);
			}
		}
		
		public function jumpOn(forceLength:Number, forceVec:Point):void
		{
			hasFrog = true;
			
			if (data.isTurtle)
			{
				_turtleEnabled = true;
			}
			
			if (!data.isStatic)
			{
				_model.addWave(pos.x, pos.y, forceLength, this);
				
				if (!data.isCarrier)
				{
					angVel += (-FrogModel.ANG_VEL_PUSH_FORCE + (Math.random() * 2 * FrogModel.ANG_VEL_PUSH_FORCE)) * forceLength;
					_forces.push(forceVec);
				}
			}
			
			if (hasStar)
			{
				starCollected();
			}
			else if (hasGoal)
			{
				goalCollected();
			}
			else
			{
				_model.resetCombo();
			}
		}
		
		public function addForce(force:Point):void
		{
			_forces.push(force);
		}
		
		public override function update():void
		{
			if (!data.isStatic)
			{
				// bounded lilly.
				if (data.isBounded)
				{
					boundVector = new Point(data.pos.x, data.pos.y).subtract(pos);
					if (boundVector.length > data.boundRange)
					{
						var boundForce : Point = boundVector.clone();
						boundForce.normalize(_linVel.length)
						_forces.push(boundForce);
						angVel *= 0.5;
					}
				}
				else if (data.isCarrier && hasFrog)
				{
					_forces.push(Point.polar(data.carrierForce, data.carrierAng));
				}
				
				// forces.
				var totalForce : Point = new Point();
				for each (var force : Point in _forces)
				{
					totalForce = totalForce.add(force);
				}
				
				// linear speed.
				_linVel = _linVel.add(totalForce);
				var newSpeed : Number = _linVel.length - LINEAR_DAMPING;
				if (newSpeed > MAX_LIN_VEL)
				{
					_linVel.normalize(newSpeed);
				}
				else 
				{
					if (newSpeed < 0)
					{
						newSpeed = 0;
					}
					_linVel.normalize(newSpeed);
				}
				
				pos = pos.add(_linVel);
				
				// angular velocity.
				if (angVel > MAX_ANG_VEL)
				{
					angVel = MAX_ANG_VEL;
				}
				else if (angVel < -MAX_ANG_VEL)
				{
					angVel = -MAX_ANG_VEL;
				}
				
				if (angVel > 0.0)
				{
					angVel -= ANGULAR_DAMPING;
					if (angVel < MIN_ANG_VEL) 
					{
						angVel = MIN_ANG_VEL;
					}
				}
				else if (angVel < 0.0)
				{
					angVel += ANGULAR_DAMPING;
					if (angVel > -MIN_ANG_VEL) 
					{
						angVel = -MIN_ANG_VEL;
					}
				}
				
				// turtle
				if (_turtleEnabled)
				{
					_turtleTimer++;
					if (_turtleTimer == _maxTurtleTimer)
					{
						_turtleTimer = 0;
						_model.addWave(pos.x, pos.y, TURTLE_FORCE, this);
					}
				}
				
				// reset forces.
				if (_forces.length > 0)
				{
					_forces = new Vector.<Point>();
				}
				
				// fade time.
				if (data.isFading)
				{
					if (hasFrog)
					{
						_fadingTimer++;
						if (_fadingTimer == _maxFadingTime)
						{
							isDead = true;
							_model.frog.isDead = true;
							_model.addEvent(GameEvent.LILLY_FADE, this);
						}
					}
					else if (_fadingTimer > 0)
					{
						_fadingTimer = 0;
					}
				}
			}
		}
		
		public function get fadeValue():Number
		{
			return 1 - (_fadingTimer / _maxFadingTime)
		}
		
		public function get isForceable():Boolean
		{
			return !data.isCarrier && !data.isStatic;
		}
	}
}